package demo4

import (
	"fmt"
	"sort"
)

/*
	排序（借助sort.Interface接口）
	1. Go语言的 sort.Sort 函数不会对具体的序列和它的元素做任何假设。
		相反，它使用了一个接口类型 sort.Interface 来指定通用的排序算法和可能被排序到的序列类型之间的约定。
		这个接口的实现由序列的具体表示和它希望排序的元素决定，序列的表示经常是一个切片。

	2. 一个内置的排序算法需要知道三个东西：序列的长度，表示两个元素比较的结果，一种交换两个元素的方式；
		这就是 sort.Interface 的三个方法：
		package sort
		type Interface interface {
				Len() int            // 获取元素数量
				Less(i, j int) bool // i，j是序列元素的指数。
				Swap(i, j int)        // 交换元素
		}

	3. 为了对序列进行排序，我们需要定义一个实现了这三个方法的类型，然后对这个类型的一个实例应用 sort.Sort 函数。
		思考对一个字符串切片进行排序，这可能是最简单的例子了。下面是这个新的类型 MyStringList  和它的 Len，Less 和 Swap 方法
		type MyStringList  []string
		func (p MyStringList ) Len() int { return len(m) }
		func (p MyStringList ) Less(i, j int) bool { return m[i] < m[j] }
		func (p MyStringList ) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
*/

/* demo1 */
// 将[]string定义为MyStringList类型
// 接口实现不受限于结构体，任何类型都可以实现接口。
// 要排序的字符串切片 []string 是系统定制好的类型，无法让这个类型去实现 sort.Interface 排序接口。
// 因此，需要将 []string 定义为自定义的类型。
type myStringList []string

// 实现 sort.Interface 接口的获取元素数量方法 Len
func (m myStringList) Len() int {
	return len(m)
}

// 实现 sort.Interface 接口的比较元素方法 Less
func (m myStringList) Less(i, j int) bool {
	return m[i] < m[j]
}

// 实现 sort.Interface 接口的交换元素的方法 Swap
func (m myStringList) Swap(i, j int) {
	m[i], m[j] = m[j], m[i]
}

/*  demo31: 完整实现sort.Interface进行结构体排序 */

// HeroKind 声明英雄的分类
type HeroKind int

// 定义 HeroKind 常量， 类似于枚举
const (
	None HeroKind = iota
	Tank
	Assassin
	Mage
)

// Hero 定义影响名单的结构
type Hero struct {
	Name string
	Kind HeroKind
}

// Heros 将英雄指针的切片定义为 Heros 类型
type Heros []*Hero

// Len 实现 sort.Interface 接口去元素数量方法
func (h Heros) Len() int {
	return len(h)
}

// Less 实现 sort.Interface 接口比较元素方法
func (h Heros) Less(i, j int) bool {
	// 如果英雄的分类不一致时，优先对分类进行排序
	if h[i].Kind != h[j].Kind {
		return h[i].Kind < h[j].Kind
	}

	// 默认按英雄名字字符升序排列
	return h[i].Name < h[j].Name
}

// 实现sort.Interface接口交换元素方法
func (h Heros) Swap(i, j int) {
	h[i], h[j] = h[j], h[i]
}

// TestDemo47 sort.Interface
func TestDemo47() {
	/* demo1: 使用sort.Interface接口进行排序 */

	// 准备乱序的 []string 字符串切片
	names := myStringList{
		"3. Wednesday",
		"5. Friday",
		"2. Tuesday",
		"4. Thursday",
		"1. Monday",
		"7. Sunday",
		"6. Saturday",
	}

	// 使用 sort 包进行排序
	sort.Sort(names)

	// 遍历打印结果
	for _, v := range names {
		fmt.Printf("%s\n", v)
	}

	/* demo2: 常见类型的便捷排序 */

	// demo21: 字符串切片的便捷排序, sort 包中有一个 StringSlice 类型
	names21 := sort.StringSlice{
		"3. Wednesday",
		"5. Friday",
		"2. Tuesday",
		"4. Thursday",
		"1. Monday",
		"7. Sunday",
		"6. Saturday",
	}

	sort.Sort(names21)

	// demo22:
	// sort 包在 sort.Interface 对各类型的封装上还有更进一步的简化，下面使用 sort.Strings 继续对 demo21 进行简化
	names22 := []string{
		"3. Wednesday",
		"5. Friday",
		"2. Tuesday",
		"4. Thursday",
		"1. Monday",
		"7. Sunday",
		"6. Saturday",
	}

	sort.Strings(names22)

	// 遍历打印结果
	for _, v := range names22 {
		fmt.Printf("%s\n", v)
	}

	// demo23: 对整型切片进行排序, 使用 sort.IntSlice 进行整型切片的排序
	intList := []int{99, 103, 44, 55, 33}

	sort.Ints(intList)

	fmt.Println(intList)

	// demo3: sort包内建的类型排序接口一览
	// 1. sort.Strings(a [] string), sort.Ints(a []int), sort.Float64s(a []float64)
	// 2. 编程中经常用到的 int32、int64、float32、bool 类型并没有由 sort 包实现，使用时依然需要开发者自己编写。

	/*
		demo3: 对结构体数据进行排序
			1. 结构体比基本类型更为复杂，排序时不能像数值和字符串一样拥有一些固定的单一原则。
			2. 结构体的多个字段在排序中可能会存在多种排序的规则，例如，结构体中的名字按字母升序排列，数值按从小到大的顺序排序。
	*/

	// demo31: 完整实现sort.Interface进行结构体排序
	// 准备英雄列表
	heros := Heros{
		&Hero{"吕布", Tank},
		&Hero{"李白", Assassin},
		&Hero{"妲己", Mage},
		&Hero{"貂蝉", Assassin},
		&Hero{"关羽", Tank},
		&Hero{"诸葛亮", Mage},
	}

	// 使用 sort 排序
	sort.Sort(heros)

	// 遍历英雄列表打印排序结果
	for _, v := range heros {
		fmt.Printf("%+v\n", v)
	}

	/*
		demo32: 使用sort.Slice进行切片元素排序
			1. 从 Go 1.8 开始，Go语言在 sort 包中提供了 sort.Slice() 函数进行更为简便的排序方法。
				sort.Slice() 函数只要求传入需要排序的数据，以及一个排序时对元素的回调函数，类型为 func(i,j int)bool，sort.Slice()
				函数的定义如下：
				func Slice(slice interface{}, less func(i, j int) bool)
	*/

	// 使用 sort.Slice()  优化 demo31
	heros1 := Heros{
		&Hero{"吕布", Tank},
		&Hero{"李白", Assassin},
		&Hero{"妲己", Mage},
		&Hero{"貂蝉", Assassin},
		&Hero{"关羽", Tank},
		&Hero{"诸葛亮", Mage},
	}

	// Slice(slice, less func(i, j int) bool)
	sort.Slice(heros1, func(i, j int) bool {
		// 优先类型排序
		if heros1[i].Kind != heros1[j].Kind {
			return heros1[i].Kind < heros1[j].Kind
		}

		// 默认名字排序
		return heros1[i].Name < heros1[j].Name
	})

	// 输出结果
	for _, v := range heros1 {
		fmt.Printf("%+v\n", v)
	}

	// tips: 使用 sort.Slice() 不仅可以完成结构体切片排序，还可以对各种切片类型进行自定义排序。

}
